/**
 * 最基本的唤醒缓冲队列实现。
 */
#include <cds/cfifo.h>
#include <cds/string.h>

size_t cfifo_free_size(struct cfifo *fifo)
{
    size_t fifo_size = cfifo_size(fifo);
    if (fifo_size > fifo->content_size) {
        return fifo_size - fifo->content_size;
    } else {
        return 0;
    }
}

void cfifo_reset(struct cfifo *fifo)
{
    fifo->content_size = 0;
    fifo->read_ptr = 0;
    fifo->write_ptr = 0;
}

struct cfifo*
cfifo_init(struct cfifo *fifo, void *buffer, size_t buffer_size)
{
    if (!is_power_of_two(buffer_size) || buffer_size < 2) {
        /// buffer的大小必须是2的非负次方，并且不能小于2。
        fifo->buffer = NULL;
        fifo->mask = 0;
        return NULL;
    }

    fifo->buffer = buffer;
    fifo->mask = buffer_size - 1;
    cfifo_reset(fifo);
    return fifo;
}

/**
 * @brief 从cfifo里分配给定大小的缓冲区。
 * 
 * size的大小必须小于或等于当前cfifo可用缓冲区的大小。
 * 
 * @param fifo 指向cfifo的指针。
 * @param size 需要分配的缓冲区大小。
 * 
 * @return size_t 实际分配的缓冲区大小。
 */
static inline size_t
__cfifo_alloc(struct cfifo *fifo, size_t size)
{
    size_t free_size = cfifo_free_size(fifo);
    if (free_size < size) {
        return 0;
    }

    int __tmp = fifo->write_ptr + size;
    __tmp &= fifo->mask;
    fifo->write_ptr = __tmp;
    fifo->content_size += size;
    return size;
}

/**
 * @brief 从cfifo里释放给定大小的缓冲区。
 * 
 * 当size小于等于当前缓冲区内的数据大小时释放size大小的缓冲区，当其大于时直接将cfifo重置。
 * 
 * @param fifo 指向cfifo的指针。
 * @param size 需要释放的缓冲区大小。
 * 
 * @return size_t 实际释放的缓冲区大小。
 */
static inline size_t 
__cfifo_free(struct cfifo *fifo, size_t size)
{
    size_t used_size = cfifo_used_size(fifo);
    if (used_size < size) {
        cfifo_reset(fifo);
        return used_size;
    } else {
        int __tmp = fifo->read_ptr + size;
        __tmp &= fifo->mask;
        fifo->read_ptr = __tmp;
        fifo->content_size -= size;
        return size;
    }
}

/**
 * @brief 因为环形缓冲区物理上有边界，所以读取或写入的数据可能需要被分成两段。
 */
static inline void
__cfifo_get_range(
    struct cfifo *fifo, 
    int offset, 
    size_t size, 
    size_t *ret1, 
    size_t *ret2
)
{
    size_t total_size = cfifo_size(fifo);
    size_t size1 = offset + size;
    if (size1 > total_size) {
        *ret1 = total_size - offset;
        *ret2 = size1 - total_size;
    } else {
        *ret1 = size;
        *ret2 = 0;
    }
}

/**
 * @brief 将size大小的buffer里的数据复制到以cfifo指定偏移开始的缓冲区区域。
 * 
 * @param fifo 指向cfifo的指针。
 * @param offset 写入缓冲区的偏移量。
 * @param buffer 被写入的数据。
 * @param size 需要被写入数据的量。
 */
static inline void
__cfifo_put(struct cfifo *fifo, int offset, void *buffer, size_t size)
{
    size_t size1;
    size_t size2;
    __cfifo_get_range(fifo, offset, size, &size1, &size2);
    memcpy(fifo->buffer + offset, buffer, size1);
    memcpy(fifo->buffer, buffer + size1, size2);
}

size_t cfifo_get(struct cfifo *fifo, void *buffer, size_t size)
{
    size_t size1;
    size_t size2;
    size_t used_size;

    if (!cfifo_is_initialized(fifo)) {
        return 0;
    }
    used_size = cfifo_used_size(fifo);
    if (size > used_size) {
        size = used_size;
    }
    __cfifo_get_range(fifo, fifo->read_ptr, size, &size1, &size2);
    memcpy(buffer, fifo->buffer + fifo->read_ptr, size1);
    memcpy(buffer + size1, fifo->buffer, size2);

    return size;
}

size_t cfifo_in(struct cfifo *fifo, void *buffer, size_t size)
{
    size_t actual_size = size;
    size_t free_size = cfifo_free_size(fifo);
    int offset = 0;
    if (free_size == 0) {
        return 0;
    }
    offset = fifo->write_ptr;
    if (actual_size > free_size) {
        actual_size = free_size;
    }
    actual_size = __cfifo_alloc(fifo, actual_size);
    __cfifo_put(fifo, offset, buffer, actual_size);

    return actual_size;
}

size_t cfifo_out(struct cfifo *fifo, void *buffer, size_t size)
{
    size_t actual_size = cfifo_get(fifo, buffer, size);
    if (actual_size > 0) {
        __cfifo_free(fifo, actual_size);
    }
    return actual_size;
}
